#include "Program.hpp"

Program::Program (std::string title)
{
  // assume init went ok
  initOk = true;

  // init the logger
  Log::Init();

  // Create an App, and make a window

  App::I().CreateAppWindow(title);

  fpsTracker = FPSTracker();
  showFPS = true;

  // create a Game object
  pWorld = new Game();

  // mouse dx,dy init
  mouseMovedLastX = 0;
  mouseMovedLastY = 0;
  mouseDraggedLastX = 0;
  mouseDraggedLastY = 0;
  mouseLeftButton = false;
  mouseRightButton = false;
  mouseWasJustWarped = false;

  // NOW
  // init glew
  GLenum error = glewInit();
  if (error != GLEW_OK)
  {
      LOG_ERROR << "Error Initializing GLEW!\n";
      LOG_ERROR << "Error: " << glewGetString(error) << '\n';
      initOk = false;
  }
  if (!GLEW_ARB_vertex_buffer_object)
  {
      LOG_ERROR << "This program requires graphics card support for ARB_vertex_buffer_object\n";
      initOk = false;
  }
  if (!GLEW_EXT_texture3D)
  {
      LOG_ERROR << "This program requires graphics card support for 3D textures!\n";
      initOk = false;
  }
  if (!GLEW_ARB_vertex_shader || !GLEW_ARB_fragment_shader)
  {
      printf("This program requires GLSL support!\n");
      initOk = false;
  }

  // TODO: Remove below ...
	// Setting max number of framebuffer attachments, is it the best place to do this? Or should be
	// moved somewhere (GL context required though)
  // glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &FrameBuffer::maxColorAttachments);


  if (initOk)
  {
      Setup();

      pWorld->Init();

      srand((unsigned int)time(0));

      timer.restart();
  }

  // init the fps font and text
  if (!fpsFont.loadFromFile("./resources/fonts/default/fixed.ttf"))
    LOG_ERROR << "Could not load FPS font: ./resources/fonts/default/fixed.ttf";
  fpsText.setFont(fpsFont);
  fpsText.setCharacterSize(18);
  fpsText.setPosition(0.0f, 0.0f);
  fpsText.setColor(sf::Color::Yellow);

  sf::ContextSettings settings = App::I().GetWindow().getSettings();
  std::cout << "OpenGL version: " << settings.majorVersion << "." << settings.minorVersion << std::endl;
}

int Program::Start()
{
    if (!initOk)
        return 1;

    Resize(App::I().GetWindow().getSize().x, App::I().GetWindow().getSize().y);

    timer.restart();

    try
    {
        // while the game is running
        while(App::I().Running())
        {
            HandleInput();
            Update();
            Draw();
        }

        // if we get here that means the game has been closed
        Cleanup();
    }
    catch(...)
    {
        LOG_ERROR << "Unhandled exception in GAME::START()!";
        return 1;
    }
    return 0;
}


void Program::Cleanup()
{
    LOG_DEBUG << "Closing";
    delete pWorld;
    LOG_DEBUG << "Calling App::Cleanup";
    App::Cleanup();
}

void Program::Setup()
{
    //*******************************************************************************
    // graphics setup
    // enable transparency, and set the transparency functions
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // enable depth testing
    glEnable(GL_DEPTH_TEST);

    // always re-normalize normal (due to scale transform)
    glEnable(GL_NORMALIZE);

    //glEnable(GL_LINE_SMOOTH);
    
    // enable lighting
    // glEnable(GL_LIGHTING);

    // Set material to folow glColor values
    //glEnable(GL_COLOR_MATERIAL);
    //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_3D);

    // smooth shading
    glShadeModel(GL_SMOOTH);
        
    //glMatrixMode(GL_PROJECTION);
    //glLoadIdentity();

    //Camera* pCamera = Camera::GetInstance();
    //gluPerspective(pCamera->GetFoV(), pCamera->GetAspect(), pCamera->GetNearPlane(), pCamera->GetFarPlane());
    //// setup viewing matrix
    //glMatrixMode(GL_MODELVIEW);
    //glLoadIdentity();

    //glEnable(GL_CULL_FACE);
    //glCullFace(GL_BACK);

    //glPolygonMode(GL_FRONT, GL_FILL);    
    //glPolygonMode(GL_BACK, GL_FILL);    

    //glViewport(0, 0, App::I().GetWindow().getSize().x, App::I().GetWindow().getSize().y);
    //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    //
    //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    //// use white to clean background
    //glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    //glDrawBuffer(GL_BACK);
    //glReadBuffer(GL_BACK);
    //*******************************************************************************
}

void Program::Resize(int w, int h)
{
  // handle the Resize properly (used to be in World)
  // switch so that our matrix operations affect the projection

  App::I().GetWindow().setView(sf::View(sf::FloatRect(0.0f, 0.0f, w, h)));
  
  glMatrixMode(GL_PROJECTION); 
  glLoadIdentity();
  Camera* pCamera = Camera::GetInstance();
  pCamera->UpdateAspect(w, h);

  gluPerspective(pCamera->GetFoV(), pCamera->GetAspect(), pCamera->GetNearPlane(), pCamera->GetFarPlane());
  //glOrtho(-2, 2, -2, 2, 1, 10000);

  // reset parameters
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glViewport(0,0, w, h);

  mi::Interface::I().OnResize(w,h);
  pWorld->Resize(w,h);
  //Console::Instance()->Resize();
  //Draw();
}

void Program::HandleInput()
{
//    DBG_FN;
    sf::Event Event;
    while (App::I().GetWindow().pollEvent(Event))
    {
        switch(Event.type)
        {
        case sf::Event::Closed :
          App::I().Exit();
          break;
        case sf::Event::Resized :
          Resize(Event.size.width,Event.size.height);
          break;
        case sf::Event::KeyPressed :
          KeysDown(Event.key);
          break;
        case sf::Event::KeyReleased :
          KeysUp(Event.key);
          break;
        case sf::Event::MouseWheelMoved :
          MouseWheel(Event.mouseWheel.delta);
          break;
        case sf::Event::MouseButtonPressed :
          MouseClick(Event.mouseButton.button, sf::Down, Event.mouseButton.x, Event.mouseButton.y);
          break;
        case sf::Event::MouseButtonReleased :
          MouseClick(Event.mouseButton.button, sf::Up, Event.mouseButton.x, Event.mouseButton.y);
          break;
        case sf::Event::MouseMoved :
          if (mouseRightButton || mouseLeftButton)
          {
              MouseDragged(Event.mouseMove.x, Event.mouseMove.y);
          }
          else
          {
              MouseMoved(Event.mouseMove.x, Event.mouseMove.y);
          }
          break;
        case sf::Event::LostFocus :
          pWorld->FocusChanged(false);
          break;
        case sf::Event::GainedFocus :
          pWorld->FocusChanged(true);
          break;
        case sf::Event::TextEntered :
          TextEntered(Event.text.unicode);
          break;
        }
    }

}

void Program::Update()
{
//    DBG_FN;
  // World function update 
  const double fixedFrameTime = 1/60.0;
  if( timer.getElapsedTime().asSeconds() > fixedFrameTime )
  {
    double dTime = (double)timer.getElapsedTime().asSeconds();
    timer.restart();
    mi::Interface::I().Update(dTime);
    pWorld->Update(dTime); 
    //Console::Instance()->Update(dTime);
  }
  
  // if the mouse is 'captured'
  if (App::I().MouseMode() == App::Capture)
  {
    mouseDraggedLastX = App::I().GetWindow().getSize().x/2;
    mouseDraggedLastY =  App::I().GetWindow().getSize().y/2;
    mouseMovedLastX = mouseDraggedLastX;
    mouseMovedLastY = mouseDraggedLastY;
    sf::Mouse::setPosition(sf::Vector2i(mouseDraggedLastX, mouseDraggedLastY)); 
    App::I().GetWindow().setMouseCursorVisible(false);
  }
  else
  {
    App::I().GetWindow().setMouseCursorVisible(true);
  }
}

void Program::Draw()
{            
//  DBG_FN;
  // clear the buffers
  //glClearColor((float)App::I().ClearColor().x,
  //             (float)App::I().ClearColor().y,
  //             (float)App::I().ClearColor().z,
  //             1.0f);
  //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  App::I().GetWindow().clear(sf::Color(128, 128, 128));
    
  //glPushMatrix();
  //  pWorld->Draw();
  //glPopMatrix();

  //glDisable(GL_DEPTH_TEST);
  //glMatrixMode(GL_PROJECTION);

  //glPushMatrix();
  //  glLoadIdentity();
  //  gluOrtho2D(0,App::I().GetWindow().getSize().x, 0, App::I().GetWindow().getSize().y);
  //  glDisable(GL_LIGHTING);
  //  glDisable(GL_CULL_FACE);
  //  glBindTexture(GL_TEXTURE_2D, 0);
  //  glBindTexture(GL_TEXTURE_3D, 0);
  //  glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
  //  glEnable(GL_TEXTURE_2D);


  //Console::Instance()->Draw();

  pWorld->Draw2d();

  //App::I().GetWindow().pushGLStates();
  mi::Interface::I().Draw2D();
  //App::I().GetWindow().popGLStates();
  
  // Draw FPS here    
  if (showFPS)
  {
    double fps = fpsTracker.GetFPS();
    fpsText.setString(fToS(fps));
    App::I().GetWindow().draw(fpsText);
  }

  //fpsFont->Render(fToS(fps), Vec2i(10,10), Vec3d(1.0, 1.0, 0.0));

  //glPopMatrix();


  //glEnable(GL_LIGHTING);
  //glEnable(GL_CULL_FACE);
  //glMatrixMode(GL_MODELVIEW);
  //glEnable(GL_DEPTH_TEST);

  // increase FPS counter
  fpsTracker.FrameDrawn();
  // swap buffers here
  App::I().GetWindow().display();
}

void Program::MouseClick(sf::Mouse::Button button, sf::MouseState state, int x, int y)
{
  if (button == sf::Mouse::Right && state == sf::Down)
      mouseRightButton = true;
  else
      mouseRightButton = false;
  if (button == sf::Mouse::Left && state == sf::Down)
      mouseLeftButton = true;
  else
      mouseLeftButton = false;
  if (state == sf::Down)
  {
      mouseDraggedLastX = x;
      mouseDraggedLastY = y;
  }
  else
  {
      mouseMovedLastX = x;
      mouseMovedLastY = y;
  }

  int ay = App::I().GetWindow().getSize().y - y;
  if (!mi::Interface::I().MouseClick(button, state, x, ay))
    pWorld->MouseClick(button, state, x, ay);
}

void Program::MouseMoved(int x, int y)
{
    int dx = x - mouseMovedLastX;
    int dy = - y + mouseMovedLastY;
    int ay = App::I().GetWindow().getSize().y - y;

    if(!mi::Interface::I().MouseMoved(x, ay, dx, dy))
      pWorld->MouseMoved(x, ay, dx, dy);
    
    mouseMovedLastX = x;
    mouseMovedLastY = y;
}

void Program::MouseDragged(int x, int y)
{
    int dx = x - mouseDraggedLastX;
    int dy = - y + mouseDraggedLastY;
    int ay = App::I().GetWindow().getSize().y - y;
    
    if(!mi::Interface::I().MouseDragged(x, ay, dx, dy))
      pWorld->MouseDragged(x, ay, dx, dy);
    
    mouseDraggedLastX = x;
    mouseDraggedLastY = y;
}

void Program::MouseWheel(int delta)
{
  if (!mi::Interface::I().MouseWheel(delta))
    pWorld->MouseWheel(delta);
}

void Program::KeysDown(sf::Event::KeyEvent key)
{
  if (!mi::Interface::I().KeysDown(key))
    pWorld->KeysDown(key);
  if (key.code == sf::Keyboard::F12)
  {
      showFPS = !showFPS;
  }
}

void Program::KeysUp(sf::Event::KeyEvent key)
{
  if (!mi::Interface::I().KeysUp(key))
    pWorld->KeysUp(key);
}

void Program::TextEntered(sf::Uint32 code)
{
  if (code < 128)
  {
    if (!mi::Interface::I().TextEntered(code))
      pWorld->TextEntered(static_cast<char>(code));
  }
}